use serde::{ser::Error, Deserialize, Serialize};
use serde_json;

#[derive(Serialize, Deserialize, Debug)]
pub enum Heartbeat {
    Server(Server),
    AccessibilityService(AccessibilityService),
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Server {
    pub id: String,
    pub svid: String,
    #[serde(rename = "timestampMs")]
    pub timestamp_ms: u64,
    pub desc: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct AccessibilityService {
    pub id: String,
    pub asid: String,
    #[serde(rename = "timestampMs")]
    pub timestamp_ms: u64,
    pub desc: String,
}

impl Heartbeat {
    pub fn tname(&self) -> &str {
        match self {
            Heartbeat::Server(_) => "Server",
            Heartbeat::AccessibilityService(_) => "AccessibilityService",
        }
    }
    pub fn serialize(&self) -> Result<String, serde_json::Error> {
        let mut map = serde_json::Map::new();
        map.insert("type".into(), serde_json::Value::String(format!("com.fuda.trade.model.Heartbeat.{}", self.tname())));
        
        match self {
            Heartbeat::Server(data) => {
                let data_map = serde_json::to_value(data)?;
                map.extend(data_map.as_object().unwrap().clone());
            },
            Heartbeat::AccessibilityService(data) => {
                let data_map = serde_json::to_value(data)?;
                map.extend(data_map.as_object().unwrap().clone());
            },
           
        }

        serde_json::to_string(&map)
    }

    pub fn deserialize(json: &str) -> Result<Heartbeat, serde_json::Error> {
        let value: serde_json::Value = serde_json::from_str(json)?;
        let type_str = value["type"].as_str().ok_or_else(|| serde_json::Error::custom("Missing type field"))?;

        match type_str {
            "com.fuda.trade.model.Heartbeat.Server" => {
                let buy_auto_value = value.as_object().ok_or_else(|| serde_json::Error::custom("Expected object"))?;
                Ok(Heartbeat::Server(serde_json::from_value(serde_json::Value::Object(buy_auto_value.clone()))?))
            },
            "com.fuda.trade.model.Heartbeat.AccessibilityService" => {
                let sell_auto_value = value.as_object().ok_or_else(|| serde_json::Error::custom("Expected object"))?;
                Ok(Heartbeat::AccessibilityService(serde_json::from_value(serde_json::Value::Object(sell_auto_value.clone()))?))
            },
           
            _ => Err(serde_json::Error::custom("Unknown type")),
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use log::debug;

    #[tokio::test]
    async fn test_trade_command_serde() {
        pretty_env_logger::init_timed();
        debug!("debug No more records available; exit the loop");
        println!("print No more records available; exit the loop");

        let server_hb = Heartbeat::Server(Server {
            id: "1".to_string(),
            svid: "1".to_string(),
            timestamp_ms: 1723714331186u64,
            desc: "ServerHeartbeat@heartbeat.rs".to_string(),
        });

        // {"type":"com.fuda.trade.model.Heartbeat.Server","id":"1","svid":"1","timestampMs":1723714331186,"desc":"heartbeat@HeartbeatUnitTest.kt"}
        // {"type":"com.fuda.trade.model.Heartbeat.Server","id":"1","svid":"1","timestampMs":1723714331186,"desc":"ServerHeartbeat@heartbeat.rs"}
        let json = server_hb.serialize().unwrap();
        println!("serialized json: \n{}", json);

        // Deserialize the JSON back into a Heartbeat
        let server_hb_de: Heartbeat = Heartbeat::deserialize(&json).unwrap();

        // Check that the deserialized Heartbeat matches the original
        match (server_hb, server_hb_de) {
            (Heartbeat::Server(original), Heartbeat::Server(deserialized)) => {
                assert_eq!(original.id, deserialized.id);
                assert_eq!(original.svid, deserialized.svid);
                assert_eq!(original.timestamp_ms, deserialized.timestamp_ms);
                assert_eq!(original.desc, deserialized.desc);
            },
            _ => panic!("Deserialized Heartbeat does not match the original"),
        }
    }
}